<?php
/* 单元测试
*
*
*/

namespace hpnWse\nMvc {

require_once(\hpnWse\fGetWseDiry() . 'hpnWse/nMvc/(1)FvMgr.php');

use \hpnWse\stNumUtil;
use \hpnWse\stStrUtil;
use \hpnWse\stRgxUtil;
use \hpnWse\stAryUtil;
use \hpnWse\stObjUtil;
use \hpnWse\stFctnUtil;

/// 领域模型
class tDmdl extends tMdl
{
	public function __construct($a_Json = null, $a_Dsrlz = false)
	{
		parent::__construct($a_Json, $a_Dsrlz);
	}

	//========================= 静态接口

	/// 为验证而新建
	/// a_FullTpnm：String，完全类型名
	/// a_Json：String$Object，JSON
	/// 返回：派生类型，全部字段接受错误，以便验证
	public static function sdNewForVldt($a_FullTpnm, $a_Json)
	{
		if (is_string($a_Json))
		{
			$a_Json = \hpnWse\stObjUtil::cDcdJson($a_Json);			
		}

		$a_FullTpnm = \hpnWse\stObjUtil::cEnsrFullTpnm($a_FullTpnm);
		$l_Rst = new $a_FullTpnm($a_Json, true);
		$l_Rst->cAllFldsAcpErr(true);
		return $l_Rst;
	}

	/// 从数据库读取
	/// $a_Cfg:
	/// {
	/// c_Pdo: PDO
	/// c_Tbl: String，表格
	/// c_Cols：String，列，格式同SQL，占位符的名称与之相同（只是前带冒号）
	/// 	可把“int ”放在列名前，返回值将被转成int，如"name, int age, gender, ..."
	/// c_Pick：String，挑选，如"WHERE id = 3"，关键字可省
	/// c_Sort：String，排序，如"ORDER BY name DESC"，关键字可省
	/// c_Rge：String，范围，如"LIMIT 20, 10"，关键字可省
	/// c_Agms：Array，实参
	/// }
	/// 返回：PureObj[]，即行数组
	public static function sdReadFromDb($a_Cfg)
	{
		$l_Cols = $a_Cfg['c_Cols'];
		$l_IntCols = null;
		\hpnWse\stSqlUtil::cExtrTypedCols($l_Cols, $l_IntCols);

		$l_Pick = \hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_Pick', '');
		$l_Sort = \hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_Sort', '');
		$l_Rge = \hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_Rge', '');
		$l_Agms = \hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_Agms');

		$l_PdoStmt = \hpnWse\stSqlUtil::cReadCols($a_Cfg['c_Pdo'], $a_Cfg['c_Tbl'], 
			$l_Cols, $l_Pick, $l_Sort, $l_Rge, $l_Agms);
		$l_Rst = \hpnWse\stSqlUtil::cFchAll($l_PdoStmt);
		if (\hpnWse\fBool($l_IntCols))
		{
			\hpnWse\stSqlUtil::cIntCols($l_IntCols, $l_Rst);
		}
		return $l_Rst;
	}

	/// 从数据库读取主键
	/// c_Pdo: PDO
	/// c_Tbl: String，表格
	/// c_PkCols：String，主键列，用逗号分隔多列
	/// c_ByCols：String，根据列？用逗号分隔多列
	/// c_Agms：Array，实参
	/// c_AtMostOne：Boolean，至多一个？
	/// 返回：null表示不存在，非null时，若主键列只有一列则返回主键值，否则返回主键数组
	public static function sdReadPkFromDb($a_Cfg)
	{
		$l_PkCols = $a_Cfg['c_PkCols'];
		$l_Mlt = (\hpnWse\stStrUtil::cFind($l_PkCols, ',') >= 0);
		$l_ByCols = \hpnWse\stSqlUtil::cBldPlchds_PkList($a_Cfg['c_ByCols']);
		$l_Rst = self::sdReadFromDb(array(
			'c_Pdo' => $a_Cfg['c_Pdo'],
			'c_Tbl' => $a_Cfg['c_Tbl'],
			'c_Cols' => $l_PkCols,
			'c_Pick' => $l_ByCols,
			'c_Agms' => $a_Cfg['c_Agms']
		));
		if (\hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_AtMostOne'))
		{
			if (0 == count($l_Rst)) { return null; }
			return $l_Mlt ? $l_Rst[0] : $l_Rst[0][$l_PkCols];
		}
		return $l_Rst;
	}

	/// 创建到数据库，【注意】当任何一项未通过验证，都不会录入数据库
	/// $a_Cfg:
	/// {
	/// c_VldtMdl：tMdl，验证模型，若不提供则不验证
	/// c_Data：JSON$JSON[]，数据JSON（数组）
	/// c_Ovrds: Object，键值对，覆盖c_Data里的值
	/// c_Pdo: PDO
	/// c_Tbl: String，表格
	/// c_Cols：String，列，格式同SQL，占位符的名称与之相同（只是前带冒号）
	/// c_Ignr：Boolean，忽略重复？
	/// c_AutoId：Boolean，自动ID？
	/// }
	/// 返回：错误信息，null表示没有错误
	public static function sdCrtToDb($a_Cfg)
	{
		// 重置
		self::$sc_AutoIds = array();
		$l_AutoId = \hpnWse\fBool(\hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_AutoId', false));

		// 数据必须是纯对象或数组
		if (! \hpnWse\fIsPureObjOrAry($a_Cfg['c_Data']))
		{ throw new \Exception("“c_Data”必须是纯对象或数组", -1); }

		// 数据统一为数组形式
		$l_Data = \hpnWse\stAryUtil::cNewIfOne($a_Cfg['c_Data'], true);
		if (\hpnWse\stAryUtil::cIsEmt($l_Data))
		{ return null; }

		// 先验证
		$l_VM = \hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_VldtMdl');
		$l_ErrInfo = array();
		$l_VJ; // 通过验证的JSON
		if ($l_VM)
		{
			if ((! self::sdVldtDataJsons($l_ErrInfo, $l_VJ, $l_VM, $l_Data)))
			{ return $l_ErrInfo; }
		}
		else // 无需验证，直接使用传入的数据
		{
			$l_VJ = &$l_Data;
		}

		// 执行至此，说明全部通过验证，可以放心写入数据库了！
		$l_ColAry = \hpnWse\stSqlUtil::cColAryFromStr($a_Cfg['c_Cols']);
		$l_PkAry = array();
		$l_Ovrds = \hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_Ovrds');
		$l_TJ;
		$l_Pdo = $a_Cfg['c_Pdo'];
		$l_PdoStmt = self::sdPdoPrpr_Insert($a_Cfg);
		for ($i=0; $i<count($l_VJ); ++$i)
		{
			$l_VJi = &$l_VJ[$i];
			self::sdBldTempJson($l_TJ, $l_PkAry, $l_ColAry, $l_Ovrds, $l_VJi);
			$l_PdoStmt->execute($l_TJ);
			if ($l_AutoId)
			{ self::$sc_AutoIds[] = strval($l_Pdo->lastInsertId()); }
		}
		return null;
	}

	//【lastInsertId规则：】
	// INSERT INTO ... ON DUPLICATE KEY UPDATE 
	// (something like id=id or equivalent, where the updated value is equal to the original one), this is what I'm getting:
 // - If the key didn't exist, the value is inserted, and lastInsertId() returns the expected id for the new row.
 // - If the row exists AND a value is updated, with lastInsertId() I get the ID of the updated row.
 // - If the row exists but NO value is updated, lastInsertId() returns 0.


	/// 自动ID，由sdCrtToDb管理
	public static $sc_AutoIds = array();

	/// 更新到数据库，【注意】当任何一项未通过验证，都不会录入数据库
	/// $a_Cfg:
	/// {
	/// c_VldtMdl：tMdl，验证模型，若不提供则不验证
	/// c_Data：JSON$JSON[]，数据JSON（数组）
	/// c_Ovrds: Object，键值对，覆盖c_Data里的值
	/// c_Pdo: PDO
	/// c_Tbl: String，表格
	/// c_Cols：String，列，格式同SQL，占位符的名称与之相同（只是前带冒号）
	/// c_Pks：String，主键，以逗号分隔多列
	/// }
	/// 返回：错误信息，null表示没有错误
	public static function sdUpdToDb($a_Cfg)
	{
		$l_Data = \hpnWse\stAryUtil::cNewIfOne($a_Cfg['c_Data'], true);
		if (\hpnWse\stAryUtil::cIsEmt($l_Data))
		{ return null; }

		// 先验证
		$l_VM = \hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_VldtMdl');
		$l_ErrInfo = array();
		$l_VJ; // 通过验证的JSON
		if ($l_VM)
		{
			if ((! self::sdVldtDataJsons($l_ErrInfo, $l_VJ, $l_VM, $l_Data)))
			{ return $l_ErrInfo; }
		}
		else // 无需验证，直接使用传入的数据
		{
			$l_VJ = &$l_Data;
		}

		// 执行至此，说明全部通过验证，可以放心写入数据库了！
		$l_ColAry = \hpnWse\stSqlUtil::cColAryFromStr($a_Cfg['c_Cols']);
		$l_PkAry = \hpnWse\stSqlUtil::cColAryFromStr($a_Cfg['c_Pks']);
		$l_Ovrds = \hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_Ovrds');
		$l_TJ;
		$l_PdoStmt = self::sdPdoPrpr_Update($a_Cfg);
		for ($i=0; $i<count($l_VJ); ++$i)
		{
			$l_VJi = &$l_VJ[$i];
			self::sdBldTempJson($l_TJ, $l_PkAry, $l_ColAry, $l_Ovrds, $l_VJi);
		//	\hpnWse\stHttpSvc::$c_Dbg['l_VJi'] = $l_VJi;
			$l_PdoStmt->execute($l_TJ);
		}
		return null;
	}

	/// 删除到数据库
	/// $a_Cfg:
	/// {
	/// c_Data：String$String[]，主键（数组）
	/// c_Pdo: PDO
	/// c_Tbl: String，表格
	/// c_Pks：String，主键，以逗号分隔多列
	/// }
	/// 返回：错误信息，null表示没有错误
	public static function sdDltToDb($a_Cfg)
	{
		$l_Data = \hpnWse\stAryUtil::cNewIfOne($a_Cfg['c_Data'], true);
		if (\hpnWse\stAryUtil::cIsEmt($l_Data))
		{ return null; }

		// 写入数据库
		$l_ColAry = array();
		$l_PkAry = \hpnWse\stSqlUtil::cColAryFromStr($a_Cfg['c_Pks']);
		$l_Ovrds = null;
		$l_TJ;
		$l_PdoStmt = self::sdPdoPrpr_Delete($a_Cfg);
		for ($i=0; $i<count($l_Data); ++$i)
		{
			$l_PkStrAry = \hpnWse\stSqlUtil::cColAryFromStr($l_Data[$i]); // "1,2,3"->["1", "2", "3"]
			self::sdBldTempJson($l_TJ, $l_PkAry, $l_ColAry, $l_Ovrds, $l_PkStrAry, true);
			$l_PdoStmt->execute($l_TJ);
		}
		return null;
	}

	/// 验证数据JSON
	/// a_VldJsons：JSON$JSON[]，经过验证的有效JSON
	/// a_VldtMdl：tMdl，验证模型
	/// a_DataJsons：JSON$JSON[]，数据JSON
	/// 返回：tMdl.cVldt的返回值，若为null则表示没有错误
	public static function sdVldtDataJsons(&$a_ErrInfo, &$a_VldJsons, $a_VldtMdl, &$a_DataJsons)
	{
		// 先验证
		$l_VldJson = null;
		if (\hpnWse\fIsAry($a_DataJsons, true))
		{
			$a_VldJsons = array();
			for ($i=0; $i<count($a_DataJsons); ++$i)
			{
				if (! self::seVldtDataJson($a_ErrInfo, $l_VldJson, $a_VldtMdl, $a_DataJsons[$i]))
				{ return false; }

				$a_VldJsons[] = $l_VldJson;
			}
			return true;
		}
		else
		{
			if (self::seVldtDataJson($a_ErrInfo, $l_VldJson, $a_VldtMdl, $a_DataJsons))
			{
				$a_VldJsons = $l_VldJson;
				return true;
			}
			else
			{
				return false;
			}
		}
	}

	/// 构建临时JSON
	/// $a_TJ，临时JSON，将被填充
	/// $a_PkAry, $a_ColAry，主键数组和列数组，注意是数组不是字符串
	/// $a_Ovrds，覆盖部分列的值
	/// $a_VJi，通过验证的数据JSON
	/// $a_IsAry，$a_VJi是否为数组？默认false表示纯对象，仅用于a_PkAry有效时
	public static function sdBldTempJson(&$a_TJ, &$a_PkAry, &$a_ColAry, &$a_Ovrds, 
										&$a_VJi, $a_IsAry = false)
	{
		$a_TJ = array();
		for ($i=0; $i<count($a_PkAry); ++$i)
		{
			$l_Pk = $a_PkAry[$i];
			$a_TJ[$l_Pk] = $a_IsAry ? $a_VJi[$i] : $a_VJi[$l_Pk];
		}
		for ($i=0; $i<count($a_ColAry); ++$i)
		{
			$l_Col = $a_ColAry[$i];
			$a_TJ[$l_Col] = \hpnWse\stObjUtil::cFchPpty($a_VJi, $l_Col, null);
			if ($a_Ovrds && \hpnWse\stObjUtil::cHasPpty($a_Ovrds, $l_Col))
			{
				$a_TJ[$l_Col] = $a_Ovrds[$l_Col];
			}
		}
	}

	/// PDO预备 - 插入
	/// $a_Cfg:
	/// {
	/// c_Pdo: PDO
	/// c_Tbl: String，表格
	/// c_Cols：String，列，格式同SQL，占位符的名称与之相同（只是前带冒号）
	/// c_Ignr：Boolean，忽略重复？
	/// }
	/// 返回：PDOStatement，execute后可用PDO::lastInsertId获取自动递增ID值
	public static function sdPdoPrpr_Insert($a_Cfg)
	{
		$l_Tbl = $a_Cfg['c_Tbl'];
		$l_Cols = $a_Cfg['c_Cols'];
		$l_Ignr = \hpnWse\fBool(\hpnWse\stObjUtil::cFchPpty($a_Cfg, 'c_Ignr')) ? 'IGNORE' : '';
		$l_Plchds = \hpnWse\stSqlUtil::cBldPlchds_AsnList($l_Cols, true);

$l_Sql = <<<SQL
INSERT $l_Ignr INTO $l_Tbl ($l_Cols) VALUES ($l_Plchds)
SQL;
		return $a_Cfg['c_Pdo']->prepare($l_Sql);
	}

	/// PDO预备 - 更新
	/// $a_Cfg:
	/// {
	/// c_Pdo: PDO
	/// c_Tbl: String，表格
	/// c_Cols：String，列，格式同SQL，占位符的名称与之相同（只是前带冒号）
	/// c_Pks：String，主键，以逗号分隔多列
	/// }
	/// 返回：PDOStatement
	public static function sdPdoPrpr_Update($a_Cfg)
	{
		$l_Tbl = $a_Cfg['c_Tbl'];
		$l_Cols = $a_Cfg['c_Cols'];
		$l_Pks = $a_Cfg['c_Pks'];
		if (! \hpnWse\fBool($l_Pks)) { throw new \Exception('“c_Pks”项无效！', -1); }
		$l_SetPlchds = \hpnWse\stSqlUtil::cBldPlchds_AsnList($l_Cols, false);
		$l_WherePlchds = \hpnWse\stSqlUtil::cBldPlchds_PkList($l_Pks);

$l_Sql = <<<SQL
UPDATE $l_Tbl
SET $l_SetPlchds
WHERE $l_WherePlchds
SQL;
	//	\hpnWse\stHttpSvc::$c_Dbg['l_Sql'] = $l_Sql;
	//	UPDATE wseappto_bookstore.t_category SET mName=:mName,mOrdinal=:mOrdinal WHERE (mId=:mId)
		return $a_Cfg['c_Pdo']->prepare($l_Sql);
	}

	/// PDO预备 - 删除
	/// $a_Cfg:
	/// {
	/// c_Pdo: PDO
	/// c_Tbl: String，表格
	/// c_Pks：String，主键，以逗号分隔多列
	/// }
	/// 返回：PDOStatement
	public static function sdPdoPrpr_Delete($a_Cfg)
	{
		$l_Tbl = $a_Cfg['c_Tbl'];
		$l_Pks = $a_Cfg['c_Pks'];
		if (! \hpnWse\fBool($l_Pks)) { throw new \Exception('“c_Pks”项无效！', -1); }
		$l_WherePlchds = \hpnWse\stSqlUtil::cBldPlchds_PkList($l_Pks);

$l_Sql = <<<SQL
DELETE FROM $l_Tbl
WHERE $l_WherePlchds
SQL;
		return $a_Cfg['c_Pdo']->prepare($l_Sql);
	}

	private static function seVldtDataJson(&$a_ErrInfo, &$a_VldJson, $a_VldtMdl, &$a_DataJson)
	{
		$a_VldtMdl->cUpdFldsFromJson($a_DataJson);
		if (! $a_VldtMdl->cVldt($a_ErrInfo))
		{ return false; }

		$a_VldJson = $a_VldtMdl->cToJson(null, false);
		$a_VldtMdl->cUpdFldsToDft();
		return true;
	}

	/// CUD事务
	/// a_fDo: void f()
	public static function sdCudTsact($a_Pdo, $a_fDo)
	{
		if ($a_Pdo->beginTransaction())
		{
			try
			{
				$a_fDo();
				$a_Pdo->commit();
			}
			catch (\Exception $a_Exc)
			{
				$a_Pdo->rollback();
				throw $a_Exc;
			}
		}
		else
		{
			throw new \Exception('PDO::beginTransaction()失败！', -1);
		}
	}
}

} // hpnWse\nMvc

//////////////////////////////////// OVER ////////////////////////////////////